// threadtest.cc 
//	Simple test case for the threads assignment.
//
//	Create two threads, and have them context switch
//	back and forth between themselves by calling Thread::Yield, 
//	to illustratethe inner workings of the thread system.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "stdio.h"
#include "ctype.h"
#include "synch.h"

extern int threadTask;
extern int SpanDigits(char c[], int i);
extern char* TrimString(char inputString[]);
extern char* GetType(char c[]);

//Begin code by Andrew Colvin and Alex Tiemann--------------------------------------------------------------------------------------------------------------------------
int numP;  //Number of people and mailboxes
int numHold; //Number of mailbox slots
int numSent = 0; //Number of messages that have been sent
int yieldcount = 0; //Number of people currently yielding
int readAll = 0; //Counter to make sure to read all messages after total messages is met
int totalSend;  //Number of total messages to be sent
char*** mailboxes;  //Double pointer array to hold mailboxes and their slots
bool* boxFull;  //Single pinter array to hold boolean values of whether or not each mailbox is full
bool deadlock;  //deadlock boolean value
Semaphore **readSemaphores;  //Double array of read semaphores
Semaphore *aReadSemaphore;  //array of read semaphores
Semaphore *areaSemaphore;  //array of area semaphores
//End code by Andrew Colvin and Alex Tiemann----------------------------------------------------------------------------------------------------------------------------


//----------------------------------------------------------------------
// SimpleThread
// 	Loop 5 times, yielding the CPU to another ready thread 
//	each iteration.
//
//	"which" is simply a number identifying the thread, for debugging
//	purposes.
//----------------------------------------------------------------------
int amountOfShouts; //global variable to control number of Shouts for threads
void
SimpleThread(int which)
{
	int num;
	for (num = 0; num < 5; num++) {
		printf("*** thread %d looped %d times\n", which, num);
		currentThread->Yield();
	}
}
	
//start code by Peter--------------------------------------------------------------------------------------------------------------------------------------------------------------- 
void Shouting (int which)
{
	for (int i = 0; i < amountOfShouts; i++)
	{
		int CPUCycles = Random();
		CPUCycles = CPUCycles % 6;
		int whichShout = Random();
		whichShout = whichShout % 7;
		printf("Thread %d shouts: ", which);
		if (whichShout == 0)
		{
			printf("It's all hakuna matata \n");
		}
		else if(whichShout == 1)
		{
			printf("Bus is slow, like a big yellow school bus \n");
		}
		else if(whichShout == 2)
		{
			printf("Beer, drinker, bar \n");
		}
		else if(whichShout == 3)
		{
			printf("C++ isn't object oriented \n");
		}
		else if(whichShout == 4)
		{
			printf("Get to the chopper! \n");
		}
		else if(whichShout == 5)
		{
			printf("The wheels on the bus go round and round \n");
		}
		else
			printf("Well that's odd \n");
		for(int j = 0; j < CPUCycles; j++)
			currentThread->Yield();
	}
}

void ShoutingThread(int notUsed)
{
	printf("Thread Task: %d\n", threadTask);
	printf("Input the number of threads you want to fork: ");
	char inputString [1000]; //allocate a large buffer
	//use fgets to help prevent buffer errors
	if (fgets(inputString, sizeof(inputString), stdin) == NULL) 
	{
		printf("Error");
		return;
	}
	char* trimmedInput = TrimString(inputString);
	char* isValidInput = GetType(trimmedInput);
	int amountOfThreads;
	//amountOfThreads is only valid for positive integers
	if (strncmp(isValidInput, "I", 6) == 0)
	{
		amountOfThreads = atoi(trimmedInput);
	}
	else
	{
		printf("Can't have a number of threads that isn't a positive integer.");
		return;
	}
	printf("Input the number of shouts each thread should yell: ");
	char shoutInput[1000];
	if (fgets(shoutInput, sizeof(shoutInput), stdin) == NULL)
	{
		printf("Error in getting number of shouts");
		return;
	}
	char* trimmedShouts =  TrimString(shoutInput);
	char* validTrim = GetType(trimmedShouts);
	//amount of shouts is only valid for positive integers
	if (strncmp(validTrim, "I", 2) == 0)
	{
		amountOfShouts = atoi(trimmedShouts);
	}	
	else
	{
		printf("Can't have a number of shouts that isn't a positive integer");
		return;
	}
	for (int i = 0; i < amountOfThreads; i++)
	{
		Thread * shoutThread = new Thread("shouter");	
		shoutThread->Fork(Shouting, i);
	}
	currentThread->Finish();
}
//end code by Peter-----------------------------------------------------------------------------------------------------------------------------------------------------------------




//start code by Peter and Ryan----------------------------------------------------------------------------------------------------------------------------------------------------
void TaskOneThread(int notUsed)
{
	char inputString [1000];
	printf("Input a number or string here: ");
	if (fgets(inputString, sizeof(inputString), stdin) == NULL)
	{
		printf("Error");
		return;
	}
	char* returnedString = TrimString(inputString);
	char* pType = GetType(returnedString);
	if (*pType == 'I')
		printf("Integer \n");
	else if (*pType == 'C')
		printf("Character String \n");
	else if (*pType == 'N')
		printf("Negative Integer \n");
	else if (*pType == 'D')
		printf("Decimal \n");
	else if (*pType == 'n')
		printf("Negative Decimal \n");
	else if (*pType == 'Z')
		printf("Null \n");
}
//end code by Peter and Ryan-----------------------------------------------------------------------------------------------------------------------------------------------------




//Begin code by Andrew Colvin and Alex Tiemann----------------------------------------------------------------------------------------------------------------------------

//****************************************
//* useableInput                         *
//* Checks to make sure input is useable *
//****************************************
int useableInput() {

	char input [1000];
	int inputValue = -1;
	if (fgets(input, sizeof(input), stdin) == NULL) 
	{
		printf("Error");
		return inputValue;
	}
	char* trimmedInput = TrimString(input);
	char* isValidInput = GetType(trimmedInput);
	if (strncmp(isValidInput, "I", 6) == 0)
	{
		inputValue = atoi(trimmedInput);
		return inputValue;
	}
	else
	{
		printf("\nInput must be a positive integer.\n");
		return inputValue;
	}

}


//*******************************************
//* Messages                                *
//* Chooses a random message		    *
//* Returns a pointer to the chosen message *
//*******************************************
char* Messages() {

		char *returnString = new char[100];
		int whichMess = Random();
		whichMess = whichMess % 5;
		switch(whichMess) {
			
			case 0: 
				returnString = "This is a happy message.\n";
				return returnString;
				break;
			
			case 1: 
				returnString = "This is a sad message.\n";
				return returnString;
				break;
				
			case 2: 
				returnString = "This is a funny message.\n";
				return returnString;
				break;
				
			case 3: 
				returnString = "This is a ugly message.\n";
				return returnString;
				break;
				
			case 4: 
				returnString = "This is a perverted message.\n";
				return returnString;
				break;
		}
		return "This is nothing.\n";

}


//**************************************************************
//* Read 						       *
//* Performs the read operations for Stage 2 Task 3 and Task 4 *
//**************************************************************
void Read(int threadID, bool &reading) {

	printf("Person %d is reading.\n", threadID);

	if(threadTask == 6){
		printf("Calling V() on Person %d's mailbox.\n", threadID);
		aReadSemaphore = readSemaphores[threadID];
	}

	//read while there is something still in the current person's mailbox
	for(int i=0; i<numHold; i++) {
		//if there is a message in the mailbox slot read it and set the slot to empty
		if(mailboxes[threadID][i] != NULL){
            		printf("Person %d read: %s in slot %d\n", threadID, mailboxes[threadID][i], i);
			mailboxes[threadID][i] = NULL;
            		reading=true;
			readAll++;

            		if(threadTask == 6)
                		aReadSemaphore->V();

            		break;
        	}
        	else {
            		printf("Nothing to read in slot %d.\n", i);
            		boxFull[threadID] = false;
            		reading = false;
			readAll++;
		}
	}

	printf("Person %d is done reading.\n", threadID);

	printf("Person %d is yielding.\n\n", threadID);
	currentThread->Yield();
	printf("Person %d is done yielding.\n\n", threadID);
}


//**************************************
//* Send			       *
//* Checks all slots for an empty slot *
//* Puts message in empty slot	       *
//**************************************
void Send(int sender, int recipient, bool &foundslot, char *message){
	
	//check all slots for the current mailbox for an empty slot
	for(int i =0; i<numHold; i++) {
		//if you find a slot send your message there
		if(mailboxes[recipient][i] == NULL){
			mailboxes[recipient][i] = message;
       			printf("Person %d sent: %s to Person %d in slot %d.\n",sender, mailboxes[recipient][i], recipient, i);
			numSent++;
			printf("Number of messages sent = %d\n\n",numSent);
			foundslot = true;
       			break;
       		}
	}
}


//***************************************************************
//* Write						      	*
//* Performs the write operations for Stage 2 Task 3 and Task 4 *
//***************************************************************
void Write(int senderID, bool &reading) {

	char* message; //points to chosen message
    	int whichP;  //has the value of which person to send a message to
    	whichP = Random();      
    	whichP = whichP % numP;
	bool foundslot = false; //boolean check for when a slot is found to put a message

	printf("Person %d is choosing a message to write.\n", senderID);

	//choose a random person to send to, if yourself try again
	while(whichP == senderID) {
        	whichP = Random();      
        	whichP = whichP % numP;
    	}

	//choose a randome message
    	message = Messages();

	printf("Person %d chose message: %s to send to person %d\n", senderID, message, whichP);

	//if Task 5 Perform Only these write operations
	if(threadTask == 5){
		//check if the current mailbox trying to send to is full
		if(boxFull[whichP]) {
			//try to send to the mailbox at most 3 times
			for(int j = 1; j<4; j++)
			{
				printf("Person %d trying to send to person %d, tried %d times--------\n", senderID, whichP, j);

				//check all slots for the current mailbox for an empty slot
				Send(senderID, whichP, foundslot, message);
				
				if(foundslot)
					break;
		        }

			//if you couldn't find a slot to put your message into
			if(!foundslot){
				//while the current mailbox you're sending to is full
				while(boxFull[whichP]) {
					//if you are in deadlock perform deadlock prevention operation
					if(deadlock) {
						printf("\n***************DEADLOCK PREVENTION INITIALIZED***************\n");
						for(int k=0; k<numP; k++)
							Read(k,reading);

						deadlock = false;
						yieldcount = 0;
						printf("\n****************DEADLOCK PREVENTION ENDED******************\n\n");
						break;
					}

					printf("Person %d yielding.\n\n", senderID);
					yieldcount++;
					currentThread->Yield();
					printf("Person %d done yielding.\n\n", senderID);

					//check to see if everyone is yielding b/c the mailbox they're trying to send to is full
					//if so you are in deadlock
					if(yieldcount == numP)
						deadlock = true;

					yieldcount--;	
				}

				//if the total amount of messages allowed to be sent has not been reached yet
				if(numSent < totalSend)
				{
					//check all slots for the current mailbox for an empty slot
					Send(senderID, whichP, foundslot, message);
				}
			}
		}
		else {
			//check all slots for the current mailbox for an empty slot
			for(int i =0; i<numHold; i++) {
				//if you find a slot send your message there
				if(mailboxes[whichP][i] == NULL){
		                        mailboxes[whichP][i] = message;
					printf("Person %d sent: %s to Person %d.\n",senderID, mailboxes[whichP][i], whichP);
					numSent++;
					printf("Number of messages sent = %d\n\n",numSent);
					if(i == numHold-1) 
						boxFull[whichP] = true;
					break;
				}
			}

			//check if all the mailboxes are full, if so set deadlock to true
			for(int j=0; j<numP; j++) {
				if(boxFull[j] == true)
					deadlock = true;
				else {
					deadlock = false;
					break;
				}
			}
		}
	}

	//if Task 6 Perform Only these write operations
	else{
		printf("Calling P() on Person %d's mailbox.\n", whichP);
		aReadSemaphore = readSemaphores[whichP];
		aReadSemaphore->P();
		//check all slots for the current mailbox for an empty slot
		Send(senderID, whichP, foundslot, message);
	}
} 


//************************************************************************************
//* MailBox                     						     *
//* Runs the read and write operations according to when they are supposed to be run *
//************************************************************************************
void MailBox(int threadID) {

	bool reading = false; //boolean check for if someone is still reading
	int readcount = 0;
	
	if(threadTask == 5)
		aReadSemaphore = readSemaphores[threadID];

	//run read and write operations while the total amount of messages to be sent has not been met
	//or run while their is still someone yielding
	while(numSent < totalSend || yieldcount > 0 ) {
	
		printf("-----------------Person %d entered the Post Office.-----------------\n", threadID);
		readcount++;
		
		if(readcount >= 1)
			areaSemaphore->P();

		Read(threadID, reading);

		while(reading)
			Read(threadID, reading);
		
		readcount--;

		if(readcount == 0)
			areaSemaphore->V();

		if(readcount == 0)
		{
			if(numSent < totalSend) 
				Write(threadID, reading);
		}

		printf("-----------------Person %d left the Post Office.-----------------\n\n", threadID);;

		int CPUCycles = Random();
		CPUCycles = CPUCycles % 6;

		//wait 2-5 cycles
		printf("Person %d is waiting 2-5 cycles.\n\n", threadID);
		for(int j = 0; j < CPUCycles; j++)
			currentThread->Yield();
	}

	printf("--------------Read All Messages------------\n");

	readAll = 0;

	while(readAll < numHold)
		Read(threadID, reading);
	
	readAll = 0;
	printf("--------------All Messages Read------------\n\n");
}


//*****************************************
//* POSim		    		  *
//* Simulates a Post Office 		  *
//* Initializes all Semaphores and Arrays *
//* Obtains Input from user		  *
//* Checks to make sure input is useable  *
//* Forks threads			  *
//*****************************************
void POSim(int notUsed) {

	printf("\nEnter the number of people: ");
	numP = useableInput();

	if(numP < 2)
		printf("You need at least 2 people.\n");
	else{
		printf("Enter the mailbox capacity: ");
		numHold = useableInput();
		
		if(numHold < 1)
			printf("You need mailboxes to have a capicity of at least 1.\n");
		else{
			printf("Enter the total number of messages to send: ");
			totalSend = useableInput();
			
			if(totalSend < 1)
				printf("People must send at least 1 message.\n");
			else{
				printf("\n");			
				mailboxes = new char **[numP];

				for(int k = 0; k<numP; k++)
					mailboxes[k] = new char *[numHold];

				boxFull = new bool [numP];
				readSemaphores = new Semaphore*[numP];

				for(int j=0; j<numP; j++) {
					readSemaphores[j] = new Semaphore("subSem", numHold);
					boxFull[j] = false;
				}
	
				aReadSemaphore = new Semaphore("aReadSemaphore",numHold);
				areaSemaphore = new Semaphore("areaSemaphore",1);
	
				for (int i = 0; i < numP; i++)
				{
					Thread * shoutThread = new Thread("person");	
					shoutThread->Fork(MailBox, i);
				}

				currentThread->Finish();
			}
		}
	}
}
//End code by Andrew Colvin and Alex Tiemann----------------------------------------------------------------------------------------------------------------------------



//Start code by Ryan Adair and Peter Campora------------------------------------------------------------------------------------------------------------------------------
bool* chopsticks;
int philosophers = 0;
int philosophersPresent = 0;
int meals = 0;
int philosophersFinished = 0;

bool RequestChopstick(int chopstickIndex, bool left)
{
	if (left)
	{
		if (chopsticks[chopstickIndex] &&  chopsticks[(chopstickIndex + 1) % philosophers])
		{
			chopsticks[chopstickIndex] = false;
			return true;
		}
		else
		{
			return false;
		}
	}
	else
	{
		if (chopsticks[chopstickIndex])
		{
			chopsticks[chopstickIndex] = false;
			return true;
		}
		else
		{
			return false;
		}
	}
}

bool ReleaseChopstick(int chopstickIndex)
{
	chopsticks[chopstickIndex] = true;
	return false;
}

void Philosopher(int philNum)
{
	philosophersPresent++;

	while(philosophersPresent != philosophers)
	{
		currentThread->Yield();
	}

	printf("Philosopher %d sits down at the table.\n", philNum);
	currentThread->Yield();

	bool leftCS = false;
	bool rightCS = false;

	while (meals > 0)
	{
		while (!leftCS && !rightCS)
		{
			while (!leftCS)
			{
				leftCS = RequestChopstick(philNum, true);
				currentThread->Yield();
			}
		
			printf("Philosopher %d picks up left chopstick.\n", philNum);
			if (philosophers == 1)
				rightCS = RequestChopstick(1, false);
			else
				rightCS = RequestChopstick((philNum + 1) % philosophers, false);

			if (rightCS)
			{
				printf("Philosopher %d picks up right chopstick.\n", philNum);
			}
			else
			{
				leftCS = ReleaseChopstick(philNum);
				printf("Philosopher %d puts down left chopstick.\n", philNum);
			}
		}
	
		printf("Philosopher %d has begun eating.\n", philNum);		

		int eating = (Random() % 4) + 2;
		
		while (eating > 0)
		{
			eating--;
			currentThread->Yield();
		}

		meals--; 
		
		leftCS = ReleaseChopstick(philNum);	
		printf("Philosopher %d puts down left chopstick.\n", philNum);

		rightCS = ReleaseChopstick((philNum + 1) % philosophers);

		printf("Philosopher %d puts down right chopstick.\n", philNum);
		printf("Philosopher %d has begun thinking.\n", philNum);	

		int thinking = (Random() % 4) + 2;

		while (thinking > 0)
		{
			thinking--;
			currentThread->Yield();
		}
	}
	
	printf("Philosopher %d is ready to leave the table.\n", philNum);
	philosophersFinished++;
	if (philosophersFinished == philosophers)
		printf("All philosophers leave together. \n");	
}

Semaphore** semChopsticks;
Semaphore** semPhilPresent;
int philPresent = 0;
void SemPhilosopher(int philNum)
{
	philPresent++;
	if (philPresent == philosophers)
	{
		for (int i = 0; i < philosophers; ++i)
		{
			if (i != philNum)
			{
				semPhilPresent[i]->V();
				printf("Philosopher %d sits down at the table.\n", i);
			}
		}

		printf("Philosopher %d sits down at the table.\n", philNum);
		currentThread->Yield();
	}
	else
	{
		semPhilPresent[philNum]->P();
		currentThread->Yield();	
	}

	while (meals > 0)
	{
		semChopsticks[philNum]->P();
		printf("Philosopher %d picks up left chopstick.\n", philNum);
		if (philosophers == 1)
			semChopsticks[1]->P();
		else
			semChopsticks[(philNum + 1) % philosophers]->P();

		printf("Philosopher %d picks up right chopstick.\n", philNum);	
		printf("Philosopher %d has begun eating.\n", philNum);		
		
		int eating = (Random() % 4) + 2;

		while (eating > 0)
		{
			eating--;
			currentThread->Yield();
		}
		meals--;
	
		semChopsticks[philNum]->V();
		printf("Philosopher %d puts down left chopstick.\n", philNum);
		
		semChopsticks[(philNum + 1) % philosophers]->V();
		printf("Philosopher %d puts down right chopstick.\n", philNum);
		printf("Philosopher %d has begun thinking.\n", philNum);
		int thinking = (Random() % 4) + 2;

		while (thinking > 0)
		{
			thinking--;
			currentThread->Yield();
		}
	}	
	printf("Philosopher %d is ready to leave the table.\n", philNum);
	philosophersFinished++;
	if (philosophersFinished == philosophers)
		printf("All philosophers leave together. \n");	
}

void DiningPhilosophers(int notUsed)
{
	printf("Please enter the number of philosophers for the project \n");
	philosophers = useableInput();
	
	if (philosophers == -1)
	{
		printf("You entered bad input for the number of philsophers \n");
		return;
	}
	
	printf("Please enter the total number of meals for all of the philsophers \n");
	meals = useableInput();
	if (meals == -1)
	{
		printf("You entered bad input for the number of meals \n");
		return;
	}
	
	//busy waiting loop dining philosophers
	if (threadTask == 3)
	{
		int numChopsticks = philosophers;
		if (philosophers > 1)
			chopsticks = new bool[philosophers]; //if one philosopher, two chopsticks
		else
		{
			chopsticks = new bool [2];
			numChopsticks = 2;
		}

		for (int i = 0; i < numChopsticks; i++)
		{
			chopsticks[i] = true;
		}
	
		for (int i = 0; i < philosophers; i++)
		{
			Thread * phil = new Thread("Philosopher");
			phil->Fork(Philosopher, i);
		}
	}
	
	//semaphore based dining philosophers
	if (threadTask == 4)
	{
		int numChopsticks = philosophers;
		semPhilPresent = new Semaphore* [philosophers];
		if (philosophers == 1)
			numChopsticks++;
		semChopsticks = new Semaphore* [numChopsticks];
		//initialize an array of Sempahore Chopsticks	
		for (int i = 0; i < numChopsticks; i++)
		{
			semChopsticks[i] = new Semaphore("Chopsticks", 1);			
		}
		for (int i = 0; i < philosophers; ++i)
		{
			semPhilPresent[i] = new Semaphore("Philosopher present", 0);
		}	
		for (int i = 0; i < philosophers; i++)
		{
			Thread* semPhil = new Thread("SemaphorePhlosopher");
			semPhil->Fork(SemPhilosopher, i);
		}
	}
}
//End code by Ryan Adair and Peter Campora--------------------------------------------------------------------------------------------------------------------------------

//Start code for Dining Philosophers with Semaphores, by Peter Campora and Ryan Adair

//Start code by Ryan Adair, Peter Campora, Alex Tiemann, Andrew Colvin---------------------------------------------------------------------------------------------
//----------------------------------------------------------------------
// ThreadTest
// 	Invoke a test routine.
//----------------------------------------------------------------------

void
ThreadTest()
{
	// Ryan - Peter
	if (threadTask == 1)
	{
		Thread *taskOne = new Thread("Thread forked for Stage 1 Task 2");
		taskOne->Fork(TaskOneThread, 0);		
	}
	else if (threadTask == 2)
	{
		Thread *ThreadForShouting = new Thread ("Thread forked for Stage 1 Task 2");
		ThreadForShouting->Fork(ShoutingThread, 0);
	}
	else if (threadTask == 3 || threadTask == 4)
	{
		Thread * DiningPhils = new Thread("Thread forked for Stage 2 Task 1 or Task 2");
		DiningPhils->Fork(DiningPhilosophers, 0);
	}
	else if (threadTask == 5 || threadTask == 6)
	{
		Thread *ThreadForBusyPostOffice = new Thread("Thread forked for Stage 2 Task 3 or 4");
		ThreadForBusyPostOffice->Fork(POSim, 0);
		
	}
	else
	{
		printf("Invalid command line arguments. -A must be specified followed by the appropriate task number.\n");
		printf(" 1 - Stage 1: Input Identification \n 2 - Stage 1: Shouting Threads \n 3 - Task 1: Dining Philosophers (Busy Waiting) \n 4 - Task 2: Dining Philosophers (Semaphores) \n 5 - Task 3: Post Office Simulation (Busy Waiting) \n 6 - Task 4: Post Office Simulation (Semaphores)\n");
	}
}
//End code by Ryan Adair, Peter Campora, Alex Tiemann, Andrew Colvin------------------------------------------------------------------------------------------------
